/*
 *  linux/kernel/rs_io.s
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 *	rs_io.s
 *
 * This module implements the rs232 io interrupts.
 */

.text
.globl rs1_interrupt,rs2_interrupt

size	= 1024				/* must be power of two !
					   and must match the value
					   in tty_io.c!!! */

/* these are the offsets into the read/write buffer structures：struct tty_queue */
rs_addr = 0
head = 4
tail = 8
proc_list = 12
buf = 16

# 当一个写缓冲队列满后，内核就会把要往写队列填字符的进程设置为等待状态。当写缓冲队列中还剩余最多 256 个字符时，中断处理程序就可以唤醒这些等待进程继续往写队列中放字符。
startup	= 256		/* chars left in write queue when we restart it 当我们重新开始写时，队列里还剩余字符个数 */

/*
 * These are the actual interrupt routines. They look where
 * the interrupt is coming from, and take appropriate action.
 */
.align 2
rs1_interrupt:
	pushl $table_list+8   /* 入参：串行接口1的读队列的指针*/
	jmp rs_int
.align 2
rs2_interrupt:
	pushl $table_list+16  /* 入参：串行接口2的读队列的指针*/
rs_int:
	pushl %edx
	pushl %ecx
	pushl %ebx
	pushl %eax
	push %es
	push %ds		/* as this is an interrupt, we cannot */
	pushl $0x10		/* know that bs is ok. Load it */
	pop %ds         /* 内核数据段 */
	pushl $0x10
	pop %es
	movl 24(%esp),%edx  /* 串行接口1或接口2的读队列指针 */
	movl (%edx),%edx    /* struct tty_queue  串行接口1或接口2的读队列 */
	movl rs_addr(%edx),%edx  /* rs-232串口 端口基地址 */
	addl $2,%edx		/* interrupt ident. reg   读中断标识寄存器IIR （port+2）*/
rep_int:
	xorl %eax,%eax
	inb %dx,%al    /* 取中断标识字节，以判断中断来源（有4种中断情况）*/
	testb $1,%al
	jne end  /* 不等于0，即位0置位：表示无待处理的中断 ，跳转到退出处理end处 */
	cmpb $6,%al		/* this shouldn't happen, but ... */
	ja end  /* 如果al大于6 ，不是合法的中断来源范围，跳转到退出处理end处 */
	# al为2~6，即：(0~3)*2
	movl 24(%esp),%ecx  # 调用子程序之前把缓冲队列指针放入 ecx
	pushl %edx			# 临时保存中断标识寄存器端口地址IIR （port+2）
	subl $2,%edx        # 将edx恢复为端口基地址，方便在调用函数中使用
	call *jmp_table(,%eax,2)		/* NOTE! not *4, bit0 is 0 already */
	popl %edx
	jmp rep_int   # 循环处理：继续判断有无待处理中断并作相应处理，直到读中断标识寄存器IIR （port+2）的位0为1退出中断调用
end:	movb $0x20,%al  # 中断退出处理。向中断控制器发送结束中断指令 EOI.
	outb %al,$0x20		/* EOI */
	pop %ds
	pop %es
	popl %eax
	popl %ebx
	popl %ecx
	popl %edx
	addl $4,%esp		# jump over _table_list entry  丢弃队列指针地址
	iret

/*
各中断类型处理子程序地址跳转表，共有4种中断来源：
modem 状态变化中断，写字符中断，读字符中断，线路状态有问题中断。
以下的程序中可以直接使用： edx（串口的端口基地址）、ecx（缓冲队列指针）
*/
jmp_table:
	.long modem_status,write_char,read_char,line_status

.align 2
/*
由于 modem 状态发生变化而引发此次中断。通过读 modem 状态寄存器 MSR 对其进行复位操作
*/ 
modem_status:
	addl $6,%edx		/* clear intr by reading modem status reg */
	inb %dx,%al
	ret
/*
由于线路状态发生变化而引起这次串行中断。通过读线路状态寄存器 LSR 对其进行复位操作
*/
.align 2
line_status:
	addl $5,%edx		/* clear intr by reading line status reg. */
	inb %dx,%al
	ret

.align 2
/*
由于 UART 芯片接收到字符而引起这次中断。对接收缓冲寄存器执行读操作可复位该中断源。

这个子程序将接收到的字符放到读缓冲队列 read_q 头指针（head)处，并且让该指针前移一个字符位置。
若 head 指针己经到达缓冲区末端，则让其折返到缓冲区开始处。
最后调用C函数 do_tty_interrupt(）（也即 copy_to_cooked()），把读入的字符经过处理放入规范模式缓冲队列（辅助缓冲队列 secondary)中。
*/
read_char:
	inb %dx,%al				# 从读接收缓存寄存器RBR（含有收到的字符）中读取1个字符到al寄存器中
	movl %ecx,%edx
	subl $table_list,%edx
	shrl $3,%edx             # 差值/8，得串口号。对于串口1是1，对于串口2是2。
	movl (%ecx),%ecx		# read-queue
	movl head(%ecx),%ebx
	movb %al,buf(%ecx,%ebx)  # 将al中的字符放入到buf[head]处
	incl %ebx                # 将头指针前移（右移）1个字节
	andl $size-1,%ebx        # 若 head 指针己经到达缓冲区末端，则让其折返到缓冲区开始处
	cmpl tail(%ecx),%ebx     # 缓冲区头指针与尾指针比较
	je 1f                    # 如果指针移动后相等，表示缓冲区满，不保存头指针，跳转 （不递增head值，直接调用do_tty_interrupt）？？？
	movl %ebx,head(%ecx)     # 更新head值
1:	pushl %edx               # 将串口号压栈，作为参数
	call do_tty_interrupt    # 调用 tty 中断处理C函数
	addl $4,%esp             # 丢弃入栈参数，并返回
	ret

/*
由于设置了发送保持寄存器允许中断标志而引起此次中断。说明对应串行终端的写字符缓冲队列中有字符需要发送。于是计算出写队列中当前所含字符数，若字符数己小于 256个，则唤醒等待写操作进程。然后从写缓冲队列尾部取出一个字符发送，并调整和保存尾指针。如果写缓冲队列己空，则跳转到 write buffer_empty 处处理写缓冲队列空的情况。
*/
.align 2
write_char:
	movl 4(%ecx),%ecx		# write-queue
	movl head(%ecx),%ebx
	subl tail(%ecx),%ebx    # 头指针 - 尾指针 = 队列中字符数
	andl $size-1,%ebx		# nr chars in queue 队列中字符数
	je write_buffer_empty
	cmpl $startup,%ebx      # 队列中字符数还超过256 个
	ja 1f
	movl proc_list(%ecx),%ebx	# wake up sleeping process 如果不超过，且等待队列不为空，则唤醒等待进程
	testl %ebx,%ebx			# is there any?
	je 1f
	movl $0,(%ebx)          # 唤醒等待进程
1:	movl tail(%ecx),%ebx
	movb buf(%ecx,%ebx),%al
	outb %al,%dx  			# 将buf尾指针处字符写入到发送保持寄存器（UART控制器寄存器）中
	incl %ebx				# tail+1
	andl $size-1,%ebx		# # 若 tail 指针己经到达缓冲区末端，则让其折返到缓冲区开始处
	movl %ebx,tail(%ecx)    # 更新tail的值
	cmpl head(%ecx),%ebx
	je write_buffer_empty   # 如果指针移动后相等，表示缓冲区空，跳转
	ret

/*
处理写缓冲队列 write_q 已空的情况。
若有等待写该串行终端的进程则唤醒之，然后屏蔽发送保持寄存器空中断，不让发送保持寄存器空时产生中断。

如果此时写缓冲队列 write_q 已空，表示当前无字符需要发送。于是我们应该做两件事情。
	首先看看有没有进程正等待写队列空出来，如果有就唤醒之。
	另外，因为现在系统已无字符需要发送，所以此时我们要暂时禁止发送保持寄存器 THR 空时产生中断。
	当再有字符被放入写缓冲队列中时，serial.c 中的 rs_write() 函数会再次允许发送保持寄存器空时产生中断， 
	因此 UART 就又会“自动〞地来取写缓冲队列中的字符，并发送出去。
*/
.align 2
write_buffer_empty:
	movl proc_list(%ecx),%ebx	# wake up sleeping process
	testl %ebx,%ebx			# is there any?
	je 1f
	movl $0,(%ebx)
	# 屏蔽发送保持寄存器空中断（位1）
1:	incl %edx			# edx为（port+1）
	inb %dx,%al
	jmp 1f
1:	jmp 1f
1:	andb $0xd,%al		/* disable transmit interrupt 屏蔽发送保持寄存器空中断（位1）*/
	outb %al,%dx
	ret
